package edu.del.newsreleasesystem.service.impl;
import edu.del.newsreleasesystem.comfirm.ConfirmCallbackService;

import java.io.IOException;
import java.time.Instant;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.del.newsreleasesystem.constant.cache.CacheKeyConstant;
import edu.del.newsreleasesystem.entity.News;
import edu.del.newsreleasesystem.mq.message.NewsSyncMessage;
import edu.del.newsreleasesystem.service.NewsService;
import edu.del.newsreleasesystem.mapper.NewsMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Service;


import java.util.concurrent.TimeUnit;

/**
* @author 27917
* @description 针对表【news(新闻信息表，存储所有发布的新闻数据)】的数据库操作Service实现
* @createDate 2025-10-21 15:47:13
*/
@Service
@Slf4j
@RequiredArgsConstructor
@EnableScheduling
public class NewsServiceImpl extends ServiceImpl<NewsMapper, News>
    implements NewsService, ConfirmCallbackService {

    private final StringRedisTemplate redisTemplate;

    private static final ObjectMapper objectMapper = new ObjectMapper();


    private final RabbitTemplate rabbitTemplate;

    // 定时任务执行器
    private final TaskScheduler taskScheduler;




    @Override
    public News getNewsById(Integer newsId) {
        // 获取 缓存键
        String key = String.format(CacheKeyConstant.NEWS_KEY_PREFIX, newsId);

        // 1. 先查询缓存
        if (redisTemplate.hasKey(key)) {
            String news = redisTemplate.opsForValue().get(key);
            if ("NULL".equals(news)) {
                // 缓存中有特殊标记，表示数据库中无此数据
                return null;
            }
            try {
               return objectMapper.readValue(news, News.class);
            } catch (JsonProcessingException e) {
                log.error("缓存数据转换失败", e);
            }

        }

        // 2. 缓存中没有，则查询数据库
        News news = this.getById(newsId);

        if (news != null) {
            try {
                redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(news));
                // 防止同一时刻多个key在同一时间过期，设置随机的过期时间
                redisTemplate.expire(key, 60 + (int) (Math.random() * 60), TimeUnit.SECONDS);
                return news;
            } catch (JsonProcessingException e) {
                throw new RuntimeException("缓存数据写入失败", e);
            }
        } else {
            // 4. 数据库也没有数据，缓存一个空标记，防止缓存穿透，设置较短过期时间
            try {
                redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
            } catch (Exception e) {
                // 即使空标记缓存失败，也不影响主流程
                log.error("缓存空标记失败", e);
            }
            return null; // 返回 null 表示新闻不存在
        }

    }

    @Override
    public boolean publishNews(News news) {
        // 1. 保存新闻到 MySQL
        boolean save = this.save(news);
        if (save) {
            log.info("新闻保存成功，ID: {}", news.getId());

            // 2. 构建同步消息
//            NewsSyncMessage message = new NewsSyncMessage(
//                    news.getId(),
//                    OperationType.PUBLISH,
//                    new Date(),
//            "SYSTEM"
//            );
//            log.info("构建同步消息成功，新闻ID: {}, 操作类型: {}", message.getNewsId(), message.getOperationType());
//
//            // 3. 设置消息唯一ID（用于追踪）
//            CorrelationData correlationData = new CorrelationData();
//            correlationData.setId(UUID.randomUUID().toString());
//
//            // 4. 发送到 RabbitMQ
//            rabbitTemplate.convertAndSend(
//                    RabbitMQConfig.TOPIC_EXCHANGE,
//                    RabbitMQConfig.SYNC_ROUTING_KEY,
//                    message,
//                    correlationData
//            );
//            log.info("已发送新闻同步消息到 RabbitMQ，新闻ID: {}", news.getId());
        } else {
            log.error("新闻保存失败，无法发送同步消息");
        }
        return save;
    }

    @Override
    public boolean deleteNews(Integer newsId) {
        // 1. 先删除缓存（第 1 次删）
        String cacheKey = String.format(CacheKeyConstant.NEWS_KEY_PREFIX, newsId);
        redisTemplate.delete(cacheKey);
        log.info("新闻删除【第1次删缓存】成功，新闻ID: {}", newsId);

        // 2. 删除数据库记录
        boolean delete = this.removeById(newsId);
        if (delete) {
            log.info("新闻删除成功，ID: {}", newsId);

//            // 3. 构建同步消息
//            NewsSyncMessage message = new NewsSyncMessage(
//                    newsId,
//                    OperationType.DELETE,
//                    new Date(),
//                    "SYSTEM"
//            );
//            log.info("构建同步消息成功，新闻ID: {}, 操作类型: {}", message.getNewsId(), message.getOperationType());

            // 4. 设置消息唯一ID
//            CorrelationData correlationData = new CorrelationData();
//            correlationData.setId(UUID.randomUUID().toString());
//
//            // 5. 发送到 RabbitMQ
//            rabbitTemplate.convertAndSend(
//                    RabbitMQConfig.TOPIC_EXCHANGE,
//                    RabbitMQConfig.SYNC_ROUTING_KEY,
//                    message,
//                    correlationData
//            );
//            log.info("已发送新闻同步（删除）消息到 RabbitMQ，新闻ID: {}", newsId);

            // 6. 延迟双删：延迟 300ms 后，再次删除缓存（第 2 次删）
            taskScheduler.schedule(() -> {
                try {
                    redisTemplate.delete(cacheKey);
                    log.info("【延迟双删第2次】新闻缓存已删除，新闻ID: {}", newsId);
                } catch (Exception e) {
                    log.error("延迟双删第2次删除新闻缓存失败，新闻ID: {}", newsId, e);
                }
            }, Instant.now().plusMillis(300)); // 延迟 300 毫秒

            return true;

        } else {
            log.error("新闻删除失败，ID: {}", newsId);
            return false;
        }
    }

    @Override
    public boolean updateNews(News news) {
        if (news.getId() == null) {
            log.error("新闻更新失败，新闻ID不能为空");
            return false;
        }

        // 1. 先删除缓存（第 1 次删）
        String cacheKey = String.format(CacheKeyConstant.NEWS_KEY_PREFIX, news.getId());
        redisTemplate.delete(cacheKey);
        log.info("新闻更新【第1次删缓存】成功，新闻ID: {}", news.getId());

        // 2. 更新数据库
        boolean update = this.updateById(news);
        if (update) {
            log.info("新闻更新成功，ID: {}", news.getId());

//            // 3. 构建同步消息
//            NewsSyncMessage message = new NewsSyncMessage(
//                    news.getId(),
//                    OperationType.UPDATE,
//                    new Date(),
//                    "SYSTEM"
//            );
//            log.info("构建同步消息成功，新闻ID: {}, 操作类型: {}", message.getNewsId(), message.getOperationType());
//
//            // 4. 设置消息唯一ID
//            CorrelationData correlationData = new CorrelationData();
//            correlationData.setId(UUID.randomUUID().toString());
//
//            // 5. 发送到 RabbitMQ
//            rabbitTemplate.convertAndSend(
//                    RabbitMQConfig.TOPIC_EXCHANGE,
//                    RabbitMQConfig.SYNC_ROUTING_KEY,
//                    message,
//                    correlationData
//            );
//            log.info("已发送新闻同步（更新）消息到 RabbitMQ，新闻ID: {}", news.getId());

            // 6. 延迟双删：延迟 300ms 后，再次删除缓存（第 2 次删）
            taskScheduler.schedule(() -> {
                try {
                    redisTemplate.delete(cacheKey);
                    log.info("【延迟双删第2次】新闻缓存已删除，新闻ID: {}", news.getId());
                } catch (Exception e) {
                    log.error("延迟双删第2次删除新闻缓存失败，新闻ID: {}", news.getId(), e);
                }
            }, Instant.now().plusMillis(300)); // 延迟 300 毫秒

            return true;

        } else {
            log.error("新闻更新失败，ID: {}", news.getId());
            return false;
        }
    }

    /**
     * 实现 ConfirmCallbackService 接口，处理消息投递到 RabbitMQ 失败的回调逻辑
     *
     * @param message 投递失败的消息（已序列化为 byte[] 的业务对象，如 NewsSyncMessage）
     */
    @Override
    public void confirmCallback(Message message) {
        try {
            // 反序列化消息内容
            byte[] body = message.getBody();
            NewsSyncMessage newsSyncMessage = objectMapper.readValue(body, NewsSyncMessage.class);

            log.error("新闻同步消息投递失败！新闻ID: {}, 操作类型: {}。将进行补偿处理...",
                    newsSyncMessage.getNewsId(), newsSyncMessage.getOperationType());

            // TODO：此处添加补偿逻辑，比如：
            // 1. 记录到数据库/日志
            // 2. 重试发送（可加入延迟队列或重试机制）
            // 3. 通知运营/开发人员
            // 4. 存入本地表等待定时任务补偿
        } catch (IOException e) {
            log.error("确认回调中反序列化消息失败！", e);
        }
    }
}




